package org.je.util;

import java.io.UnsupportedEncodingException;

import org.je.cons.JCons;

/**
 * @author fanyifeng 2012-8-31 上午8:48:13
 */
public class DynBuffer
{
	private byte[]	data_bytes; // 保存数据
	private int		data_size;	// 数据长度

	public DynBuffer() {
		this.data_bytes = new byte[16];
		this.data_size = 0;
	}

	/**
	 * @param b
	 */
	public DynBuffer(byte[] b) {
		if (b == null)
			throw new NullPointerException("b-is_null");
		this.data_bytes = b;
		this.data_size = b.length;
	}

	/**
	 * @param bt
	 */
	public void append(byte bt) {
		increase_cap(1);
		data_bytes[data_size] = bt;
		++data_size;
	}

	/**
	 * @param s
	 */
	public void append(String s) {
		if (s == null)
			throw new NullPointerException("s-is-null");
		byte[] b = null;
		try {
			b = s.getBytes(JCons.enc().utf8);
		} catch (UnsupportedEncodingException ex) {
			throw new RuntimeException(ex);
		}
		append_not_check(b, 0, b.length);
	}

	/**
	 * @param b
	 */
	public void append(byte[] b) {
		if (b == null)
			throw new NullPointerException("b-is-null");
		append_not_check(b, 0, b.length);
	}

	/**
	 * @param b
	 * @param off
	 * @param len
	 */
	public void append(byte[] b, int off, int len) {
		if (b == null)
			throw new NullPointerException("b-is-null");
		if (off < 0 || len < 0 || off + len > b.length)
			throw new IndexOutOfBoundsException("off:" + off + ",len:" + len + ",b.len:" + b.length);
		append_not_check(b, off, len);
	}

	/**
	 * @param pos
	 * @param b
	 */
	public void set(int pos, byte[] b) {
		if (pos < 0) {
			throw new IndexOutOfBoundsException("pos: " + pos);
		}
		increase_cap(pos + b.length - data_size);
		System.arraycopy(b, 0, data_bytes, pos, b.length);
	}

	/**
	 * @param bg
	 * @param ed
	 * @return bytes
	 */
	public byte[] subBytes(int bg, int ed) {
		if (bg < 0 || bg > ed || ed > data_size)
			throw new IndexOutOfBoundsException("bg:" + bg + ",ed:" + ed + ",size:" + size());
		return sub_not_check(bg, ed);
	}

	/**
	 * @param bg
	 * @param ed
	 * @return delete bytes
	 */
	public byte[] delete(int bg, int ed) {
		if (bg < 0 || bg > ed || ed > data_size)
			throw new IndexOutOfBoundsException("bg:" + bg + ",ed:" + ed + ",size:" + size());
		byte[] result = sub_not_check(bg, ed);

		System.arraycopy(data_bytes, ed, data_bytes, bg, data_size - ed);
		data_size -= (ed - bg);
		decrease_cap();
		return result;
	}

	/**
	 * @param b
	 * @param off
	 * @return index or -1
	 */
	public int indexOf(byte[] b, int off) {
		if (b == null)
			throw new NullPointerException("b-is-null");
		if (off < 0 || off > data_size)
			throw new IndexOutOfBoundsException("off:" + off + ",size:" + size());
		if (b.length == 0 || off + b.length > data_size)
			return -1;

		int matchEndIndex = data_size - b.length;
		for (int i = off; i <= matchEndIndex; ++i) {
			if (ByteUtil.match_not_check(data_bytes, b, i))
				return i;
		}
		return -1;
	}

	/**
	 * @param b
	 * @return index or -1
	 */
	public int indexOf(byte[] b) {
		return indexOf(b, 0);
	}

	private void append_not_check(byte[] b, int off, int len) {
		increase_cap(len);
		System.arraycopy(b, off, data_bytes, data_size, len);
		data_size += len;
	}

	private byte[] sub_not_check(int start, int end) {
		byte[] tem = new byte[end - start];
		System.arraycopy(data_bytes, start, tem, 0, tem.length);
		return tem;
	}

	private void increase_cap(int addLen) {
		if (data_size + addLen <= data_bytes.length)
			return;
		update_cap(data_bytes.length + NumberUtil.max(addLen, data_bytes.length));
	}

	private void decrease_cap() {
		if (data_bytes.length < 33)
			return;
		int halfCap = data_bytes.length / 2;
		if (halfCap >= data_size)
			update_cap(halfCap);
	}

	private void update_cap(int newCap) {
		byte[] tem = new byte[newCap];
		System.arraycopy(data_bytes, 0, tem, 0, data_size);
		data_bytes = tem;
	}

	public void clear() {
		this.data_bytes = new byte[16];
		this.data_size = 0;
	}

	public int size() {
		return data_size;
	}

	public int capacity() {
		return data_bytes.length;
	}

	public byte[] array() {
		return data_bytes;
	}

	public byte[] toArray() {
		byte[] dest = new byte[data_size];
		System.arraycopy(data_bytes, 0, dest, 0, data_size);
		return dest;
	}

	@Override
	public String toString() {
		try {
			return new String(data_bytes, 0, data_size, JCons.enc().utf8);
		} catch (UnsupportedEncodingException ex) {
			throw new RuntimeException(ex);
		}
	}
}
